06-01 introduction-to-charting
introduction to charting​
Charting is the second most important aspect of automatic Excel behind manipulating Ranges
. There is a bias when saying that because a lot of what I do after engineering calculations is chart the results. In particular, Excel can be used to great effect to chart time series of data. The other reason charts are so amenable to VBA is that very often you are applying the same actions to the charts. In that sense, the VBA related to charts is doing a lot of changing settings and formats so that the charts look the way you want. This ahs the immediate effect of making your charts look less like "they came from Excel" which is a common knock in some circles.
When working with Charts
, there is a Range
of difficulties depending on what you are trying to do. In some cases, working with an existing chart
is much easier than creating a new one. In other instances, it can be much simpler to create a new chart, starting from a default, rather than change all the settings back. One other major difference between Charts
and Ranges
is that working with charts is much more about knowing the object model than knowing how to program. The vast majority of your code related to charts is simply iterating through objects to find the one property that you want to change. This makes it easier to write chart VBA once you have the basics of For Each
loops down. It also means that you need to spend some time getting comfortable with the object model.
There is one oddity related to Charts that is worth mentioning now. Charts can either be embedded as an object on a Worksheet
, or they can be their own Sheets
. I personally never use the latter case, but it is common enough that it needs to be on your mind when working with Charting code.
(I don't use the Chart as a Sheet model because I find that it is not necessary in terms of displaying data. In particular, you are at the mercy of your window size and cannot easily change the dimensions. Also, it complicates the VBA side of things to work in both formats all the time, so I just decided to always put my Charts on Sheets. Your mileage may vary so I'll touch on both approaches in the code samples.)
a quick overview of the object model​
ChartObjects
->ChartObject
- this derives fromShape
and exists when the Chart is on a WorksheetChart
SeriesCollection
->Series
Axes
->Axis
ChartArea
PlotArea
ActiveChart
->Chart
- this works whether you have a Worksheet or Chart on a sheetSelection
->Variant
- this one can be useful but is often not of the type that you want.
obtaining a reference to a Chart​
When working with Charts
, the first task is typically to get a reference to an existing chart -- unless you are creating a new chart. To obtain a reference to a chart, there are a handful of ways of doing it depending on what your spreadsheet contains and how it's structured.
THe main ways to do it are:
- Use the
ActiveChart
object - Use the
Selection
object -- this is highly depending on what is selected - Use the
ChartObjects
object- If you know which chart you want, you can supply an index; this works great if there is only a single chart -
ChartObjects(1)
- If you want to do something to all charts, you can iterate this object
- If you have named the chart (more on that later) you can supply the name as the index -
ChartObjects("SomeChart")
- If you know which chart you want, you can supply an index; this works great if there is only a single chart -
- The
Workbook.Sheets
object if your charts are contained in their own sheets- Same as above, you can access via a numeric index, name, or iterate through all of them
ActiveChart
​
ActiveChart
is similar to the other Active
objects in that it does about what you expect. The one difference is that the Chart actually has to be selected or have focus in order to be considered "active". This is similar but also different to something like ActiveWorkbook
where having the workbook open makes it active.
Note that ActiveChart
will work for a Chart
that is contained on a Worksheet or also for one that is its own Sheet. If the latter case, then ActiveSheet
and ActiveChart
will refer to the same object. Side note: this technicality is why you will not get proper Intellisense when using ActiveSheet
-- that Sheet could technically be a Chart.
The nice thing about ActiveChart
is that it gives you the Chart object which then gives you immediate access to the Chart related details you are like to want to change. The downside is that unless you have a single Chart that is already selected, ActiveChart
has limited application when using VBA. Again, the goal is to avoid selecting objects in order to access them via VBA so ActiveChart
is not ideal.
Selection
​
The Selection object is probably the greatest catch all for an object. It literally holds anything, and this means that using the object requires knowing what is selected, or checking vigorously before using the object. Technically, you also let your code error out if the wrong object is selected, and this works well at times. This works well because you are unlikely to be using Selection
in a complicated workflow because, again, you should not be selecting objects to access them. This means that Selection
is really limited to one-off and helper code where you can more tightly dictate that this code only works if you select a Chart. You should still add some error handling, but sometimes that step is skipped.
Since the Selection
can hold anything, it's important to know what could be Selected. Related to charts, the following can all live in the Selection
:
ChartObjects
Chart
ChartArea
PlotArea
Legend
ChartTitle
Series
If you are writing VBA to work on Charts, you can technically require the user to select the correct part of the chart and always use Selection
. You will quickly grow tired of having to remember which part of the Chart to select in order to make the code work. To avoid this scenario, it is helpful to remember the object model and know how to work your way around a Chart.
My approach has always been to convert the Selection
to a Collection of ChartObjects
. I can then always iterate that resulting Collection to process the Charts. If only a single Chart was selected, the code works all the same. The downside to this approach is that a Chart as a Sheet cannot live inside a ChartObject. This is a large part of why I always put Charts on a Worksheet.
Below is the helper function I use in order to convert a possibly Chart containing selection into a Collection of ChartObjects
. It works for all objects except for the Axis related ones.
TODO: consider improving this code if it is included as a de facto reference
Public Function Chart_GetObjectsFromObject(ByVal inputObject As Object) As Variant
Dim chartObjectCollection As New Collection
'NOTE that this function does not work well with Axis objects. Excel does not return the correct Parent for them.
Dim targetObject As Variant
Dim inputObjectType As String
inputObjectType = TypeName(inputObject)
Select Case inputObjectType
Case "DrawingObjects"
'this means that multiple charts are selected
For Each targetObject In inputObject
If TypeName(targetObject) = "ChartObject" Then
'add it to the set
chartObjectCollection.Add targetObject
End If
Next targetObject
Case "Worksheet"
For Each targetObject In inputObject.ChartObjects
chartObjectCollection.Add targetObject
Next targetObject
Case "Chart"
chartObjectCollection.Add inputObject.Parent
Case "ChartArea", "PlotArea", "Legend", "ChartTitle"
'parent is the chart, parent of that is the chart targetObject
chartObjectCollection.Add inputObject.Parent.Parent
Case "Series"
'need to go up three levels
chartObjectCollection.Add inputObject.Parent.Parent.Parent
Case "Axis", "Gridlines", "AxisTitle"
'these are the oddly unsupported objects
MsgBox "Axis/gridline selection not supported. This is an Excel bug. Select another element on the chart(s)."
Case Else
MsgBox "Select a part of the chart(s), except an axis."
End Select
Set Chart_GetObjectsFromObject = chartObjectCollection
End Function
ChartObjects​
If you are working on a Worksheet, then that Worksheet will have the ChartObjects
object. This object is great because it contains all of the Charts in their own collection (separate from any other Shapes or buttons). This ChartObjects
collection contains object of type ChartObject. The ChartObject derives from Shape which means it contains all of the properties related to on-sheet position and size.
A typical workflow is included below since it is a pattern that shows up all the time in VBA code related to charts. At a high level the steps are:
- Use ActiveSheet or a Worksheet reference to access the
ChartObjects
- Iterate through each
ChartObject
, storing a reference to the underlying Chart - You then setup sections to work through the parts of the Chart you want
- Iterate through the
SeriesCollection
- Iterate through the Axes
- Touch the other top level properties including
ChartTile
,Legend
, etc.
- Iterate through the
This workflow is quite powerful because it can quickly be wrapped with a loop to go through all Worksheets and even possible all Workbooks. It's also powerful because you can be quite comfortable learning this pattern and then adding in the parts that you actually want to change. The only downside is that it can be quite tedious to type out all the loops every time, but there's not a good way around that other than to use the clipboard.
Another approach to using ChartObjects
is to not iterate through all of them but instead to select a single ChartObject and work with it. There are two ways to do this:
- Use an integer index for the Chart -- this is quite easy to do if there are only a few charts
- Name the chart and use that name
When using either of these approaches, it is quite helpful to show the Selection Pane
window in Excel. This pane will pop out and tell you the order and the names of all the objects on the sheet (this includes comments, shapes, and Charts). From this pane, you can rearrange the charts into the order you want or rename them.
Although For Each
loops are generally preferred when working with Charts, sometimes you simply know that you want to change one chart and an index just lets you do that. If you are in the habit of using loops however, you can easily do that with the helper code included above which stick a single chart into a Collection.
Workbook.Sheets to get Chart references​
The final approach to obtaining a Chart reference is to use the Sheets
object. Aside from ActiveChart, this is the only way to deal with Charts that are their own Sheet. Again, you can either use an index or a Name. Here, the Name is easily changed on the Sheet tab so it's much more common to use a Name when doing this. The other approach is to iterate through all the Sheets
and pick off the ones that are Charts.
There are two key points when working with Charts as Sheets
:
- You must use the
Workbook.Sheets
object to access them and notWorkbook.Worksheets
. The latter object contains only thoseWorksheets
that are not Charts. The former contains both Charts andWorksheets
. - It's possible that your Sheet is not actually a Chart. You should check the type of the object if you are going to iterate through all
Worksheets
. Also be aware that some sheets can be hidden which might lead to unexpected results.
TODO: is there a Charts object on Workbook?